package com.tyylab.kestrel.framework.core.annoutil;

import com.tyylab.kestrel.framework.core.model.anno.params.MVCMappingParams;
import com.tyylab.kestrel.framework.core.model.anno.params.MVCMethodParams;
import com.tyylab.kestrel.framework.core.model.anno.params.MVCParams;
import net.bytebuddy.description.annotation.AnnotationDescription;
import org.apache.commons.lang3.ArrayUtils;
import org.springframework.stereotype.Controller;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;

import javax.validation.Valid;

/**
 * Spring MVC Annotation Util for ByteBuddy.
 * @author tyylab
 */
public class SpringMVCAnnotationUtil {

    /**
     * Create a {@link Controller} Annotation.
     * @param name
     * @return
     */
    public static AnnotationDescription controller(String name) {
        if (StringUtils.hasText(name)) {
            return AnnotationDescription.Builder.ofType(Controller.class).define("value", name).build();
        }
        return AnnotationDescription.Builder.ofType(Controller.class).build();
    }

    /**
     * Create a {@link RestController} Annotation.
     * @param name
     * @return
     */
    public static AnnotationDescription restController(String name) {
        if (StringUtils.hasText(name)) {
            return AnnotationDescription.Builder.ofType(RestController.class).define("value", name).build();
        }
        return AnnotationDescription.Builder.ofType(RestController.class).build();
    }

    /**
     * Create a {@link GetMapping} Annotation.
     * @param name
     * @param path
     * @param params
     * @param headers
     * @param consumes
     * @param produces
     * @return
     */
    public static AnnotationDescription getMapping(String name, String[] path, String[] params, String[] headers, String[] consumes, String[] produces) {
        return httpMethod(GetMapping.class, name, path, null, params, headers, consumes, produces);
    }

    /**
     * Create a {@link PostMapping} Annotation.
     * @param name
     * @param path
     * @param params
     * @param headers
     * @param consumes
     * @param produces
     * @return
     */
    public static AnnotationDescription postMapping(String name, String[] path, String[] params, String[] headers, String[] consumes, String[] produces) {
        return httpMethod(PostMapping.class, name, path, null, params, headers, consumes, produces);
    }

    /**
     * Create a {@link PutMapping} Annotation.
     * @param name
     * @param path
     * @param params
     * @param headers
     * @param consumes
     * @param produces
     * @return
     */
    public static AnnotationDescription putMapping(String name, String[] path, String[] params, String[] headers, String[] consumes, String[] produces) {
        return httpMethod(PutMapping.class, name, path, null, params, headers, consumes, produces);
    }

    /**
     * Create a {@link DeleteMapping} Annotation.
     * @param name
     * @param path
     * @param params
     * @param headers
     * @param consumes
     * @param produces
     * @return
     */
    public static AnnotationDescription deleteMapping(String name, String[] path, String[] params, String[] headers, String[] consumes, String[] produces) {
        return httpMethod(DeleteMapping.class, name, path, null, params, headers, consumes, produces);
    }

    /**
     * Create a {@link PatchMapping} Annotation.
     * @param name
     * @param path
     * @param params
     * @param headers
     * @param consumes
     * @param produces
     * @return
     */
    public static AnnotationDescription patchMapping(String name, String[] path, String[] params, String[] headers, String[] consumes, String[] produces) {
        return httpMethod(PatchMapping.class, name, path, null, params, headers, consumes, produces);
    }

    public static AnnotationDescription methodMapping(MVCMethodParams method) {

        AnnotationDescription annot = null;
        // 多种支持请求方法
        if(method.getMethod().length > 1) {
            annot = requestMapping(method.getName(), method.getPath(),
                    method.getMethod(), method.getParams(), method.getHeaders(), method.getConsumes(), method.getProduces());
            return annot;
        }
        // 仅支持一种请求方式
        switch (method.getMethod()[0]) {
            case GET:{
                annot = getMapping(method.getName(), method.getPath(),
                        method.getParams(), method.getHeaders(), method.getConsumes(), method.getProduces());
            };break;
            case POST:{
                annot = postMapping(method.getName(), method.getPath(),
                        method.getParams(), method.getHeaders(), method.getConsumes(), method.getProduces());
            };break;
            case PUT:{
                annot = putMapping(method.getName(), method.getPath(),
                        method.getParams(), method.getHeaders(), method.getConsumes(), method.getProduces());
            };break;
            case DELETE:{
                annot = deleteMapping(method.getName(), method.getPath(),
                        method.getParams(), method.getHeaders(), method.getConsumes(), method.getProduces());
            };break;
            case PATCH:{
                annot = patchMapping(method.getName(), method.getPath(),
                        method.getParams(), method.getHeaders(), method.getConsumes(), method.getProduces());
            };break;
            default:{
                annot = getMapping(method.getName(), method.getPath(),
                        method.getParams(), method.getHeaders(), method.getConsumes(), method.getProduces());
            };break;
        }

        return annot;
    }

    /**
     * Create a {@link RequestMapping} Annotation.
     * @param mapping
     * @return
     */
    public static AnnotationDescription requestMapping(MVCMappingParams mapping) {
        return httpMethod(RequestMapping.class, mapping);
    }

    /**
     * Create a {@link RequestMapping} Annotation.
     * @param name
     * @param path
     * @param method
     * @param params
     * @param headers
     * @param consumes
     * @param produces
     * @return
     */
    public static AnnotationDescription requestMapping(String name, String[] path,
                                                       RequestMethod[] method, String[] params, String[] headers, String[] consumes, String[] produces) {
        return httpMethod(RequestMapping.class, name, path, method, params, headers, consumes, produces);
    }

    /**
     * Create a {@link CookieValue} annotation.
     * @param param
     * @return
     * @param <T>
     */
    public static <T> AnnotationDescription cookieValue(MVCParams<T> param) {
        return AnnotationDescription.Builder.ofType(CookieValue.class)
                .define("value", StringUtils.hasText(param.getName()) ? param.getName() : "")
                .define("name", StringUtils.hasText(param.getName()) ? param.getName() : "")
                .define("required", param.isRequired())
                .define("defaultValue", StringUtils.hasText(param.getDef()) ? param.getDef() : "")
                .build();
    }

    /**
     * Create a {@link MatrixVariable} annotation
     * @param param
     * @return
     * @param <T>
     */
    public static <T> AnnotationDescription matrixVariable(MVCParams<T> param) {
        return AnnotationDescription.Builder.ofType(MatrixVariable.class)
                .define("value", StringUtils.hasText(param.getName()) ? param.getName() : "")
                .define("name", StringUtils.hasText(param.getName()) ? param.getName() : "")
                .define("required", param.isRequired())
                .define("defaultValue", StringUtils.hasText(param.getDef()) ? param.getDef() : "")
                .define("pathVar", "")
                .build();
    }

    /**
     * Create a {@link PathVariable} annotation
     * @param param
     * @return
     * @param <T>
     */
    public static <T> AnnotationDescription pathVariable(MVCParams<T> param) {
        return AnnotationDescription.Builder.ofType(PathVariable.class)
                .define("value", StringUtils.hasText(param.getName()) ? param.getName() : "")
                .define("name", StringUtils.hasText(param.getName()) ? param.getName() : "")
                .define("required", param.isRequired())
                .build();
    }

    /**
     * Create a {@link RequestAttribute} annotation.
     * @param param
     * @return
     * @param <T>
     */
    public static <T> AnnotationDescription requestAttribute(MVCParams<T> param) {
        return AnnotationDescription.Builder.ofType(RequestAttribute.class)
                .define("value", StringUtils.hasText(param.getName()) ? param.getName() : "")
                .define("name", StringUtils.hasText(param.getName()) ? param.getName() : "")
                .define("required", param.isRequired())
                .build();
    }

    /**
     * Create a {@link RequestBody} annotation
     * @param param
     * @return
     * @param <T>
     */
    public static <T> AnnotationDescription requestBody(MVCParams<T> param) {
        return AnnotationDescription.Builder.ofType(RequestBody.class)
                .define("required", param.isRequired())
                .build();
    }

    public static <T> AnnotationDescription requestBody(boolean required) {
        return AnnotationDescription.Builder.ofType(RequestBody.class)
                .define("required", required)
                .build();
    }

    /**
     * Create an {@link RequestHeader} annotation
     * @param param
     * @return
     * @param <T>
     */
    public static <T> AnnotationDescription requestHeader(MVCParams<T> param) {
        return AnnotationDescription.Builder.ofType(RequestHeader.class)
                .define("value", StringUtils.hasText(param.getName()) ? param.getName() : "")
                .define("name", StringUtils.hasText(param.getName()) ? param.getName() : "")
                .define("required", param.isRequired())
                .define("defaultValue", StringUtils.hasText(param.getDef()) ? param.getDef() : "")
                .build();
    }

    /**
     * Create a {@link RequestPart} annotation
     * @param param
     * @return
     * @param <T>
     */
    public static <T> AnnotationDescription requestPart(MVCParams<T> param) {
        return AnnotationDescription.Builder.ofType(RequestPart.class)
                .define("value", StringUtils.hasText(param.getName()) ? param.getName() : "")
                .define("name", StringUtils.hasText(param.getName()) ? param.getName() : "")
                .define("required", param.isRequired())
                .build();
    }

    /*
     * 构造 @RequestParam 注解
     */
    public static <T> AnnotationDescription requestParam(MVCParams<T> param) {
        return AnnotationDescription.Builder.ofType(RequestParam.class)
                .define("value", StringUtils.hasText(param.getName()) ? param.getName() : "")
                .define("name", StringUtils.hasText(param.getName()) ? param.getName() : "")
                .define("required", param.isRequired())
                .define("defaultValue", StringUtils.hasText(param.getDef()) ? param.getDef() : "")
                .build();
    }

    /**
     * create param annotations
     * @param param
     * @return
     * @param <T>
     */
    public static <T> AnnotationDescription param(MVCParams<T> param) {
        AnnotationDescription paramAnnot = null;
        switch (param.getSource()) {
            case COOKIE: {
                paramAnnot = cookieValue(param);
            };break;
            case MATRIX: {
                paramAnnot = matrixVariable(param);
            };break;
            case PATH: {
                paramAnnot = pathVariable(param);
            };break;
            case ATTR: {
                paramAnnot = requestAttribute(param);
            };break;
            case BODY: {
                paramAnnot = requestBody(param);
            };break;
            case HEADER: {
                paramAnnot = requestHeader(param);
            };break;
            case PARAM: {
                paramAnnot = requestParam(param);
            };break;
            case PART: {
                paramAnnot =requestPart(param);
            };break;
            default: {
                paramAnnot = requestParam(param);
            };break;
        }
        return paramAnnot;
    }

    /**
     * Create a {@link Valid} annotation
     * @param param
     * @return
     * @param <T>
     */
    public static <T> AnnotationDescription valid(MVCParams<T> param) {
        return AnnotationDescription.Builder.ofType(Valid.class).build();
    }



    //////////////////////////////// private ////////////////////////////////////////////

    /**
     * Create a {@link RequestMapping} | {@link GetMapping} | {@link PostMapping} | {@link PutMapping} | {@link DeleteMapping} | {@link PatchMapping} | annotation.
     * @param annotation
     * @param mapping
     * @return
     */
    private static AnnotationDescription httpMethod(
            Class<? extends java.lang.annotation.Annotation> annotation,
            MVCMappingParams mapping) {

        AnnotationDescription.Builder builder = AnnotationDescription.Builder.ofType(annotation)
                .define("name", StringUtils.hasText(mapping.getName()) ? mapping.getName() : "")
                .defineArray("value", ArrayUtils.isNotEmpty(mapping.getPath()) ? mapping.getPath() : new String[] {})
                .defineArray("path", ArrayUtils.isNotEmpty(mapping.getPath()) ? mapping.getPath() : new String[] {})
                .defineArray("params", ArrayUtils.isNotEmpty(mapping.getParams()) ? mapping.getParams() : new String[] {})
                .defineArray("headers", ArrayUtils.isNotEmpty(mapping.getHeaders()) ? mapping.getHeaders() : new String[] {})
                .defineArray("consumes", ArrayUtils.isNotEmpty(mapping.getConsumes()) ? mapping.getConsumes() : new String[] {})
                .defineArray("produces", ArrayUtils.isNotEmpty(mapping.getProduces()) ? mapping.getProduces() : new String[] {});
        if(ArrayUtils.isNotEmpty(mapping.getMethod())) {
            builder = builder.defineEnumerationArray("method", RequestMethod.class, mapping.getMethod());
        }
        return	builder.build();
    }

    /**
     * Create a {@link RequestMapping} | {@link GetMapping} | {@link PostMapping} | {@link PutMapping} | {@link DeleteMapping} | {@link PatchMapping} | annotation.
     * @param annotation
     * @param name
     * @param path
     * @param method
     * @param params
     * @param headers
     * @param consumes
     * @param produces
     * @return
     */
    private static AnnotationDescription httpMethod(
            Class<? extends java.lang.annotation.Annotation> annotation,String name, String[] path,
            RequestMethod[] method, String[] params, String[] headers, String[] consumes, String[] produces) {

        AnnotationDescription.Builder builder = AnnotationDescription.Builder.ofType(annotation)
                .define("name", StringUtils.hasText(name) ? name : "")
                .defineArray("value", ArrayUtils.isNotEmpty(path) ? path : new String[] {})
                .defineArray("path", ArrayUtils.isNotEmpty(path) ? path : new String[] {})
                .defineArray("params", ArrayUtils.isNotEmpty(params) ? params : new String[] {})
                .defineArray("headers", ArrayUtils.isNotEmpty(headers) ? headers : new String[] {})
                .defineArray("consumes", ArrayUtils.isNotEmpty(consumes) ? consumes : new String[] {})
                .defineArray("produces", ArrayUtils.isNotEmpty(produces) ? produces : new String[] {});
        if(ArrayUtils.isNotEmpty(method)) {
            builder = builder.defineEnumerationArray("method", RequestMethod.class, method);
        }
        return	builder.build();
    }

    //////////////////////////////// private ////////////////////////////////////////////
}
